﻿using Microsoft.Phone.Net.NetworkInformation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using WeiboWPSdk.Json;
using WeiboWPSdk.ResponseEntity;

namespace WeiboWPSdk {
    /// <summary>
    /// OAuth 2.0 授权类
    /// </summary>
    public class OAuth2 {
        /// <summary>
        /// 实例化 OAuth2 类（授权）
        /// </summary>
        /// <param name="appKey">微博应用创建后得到的App Key</param>
        /// <param name="appSecret">微博应用创建后得到的App Secret</param>
        public OAuth2(string appKey, string appSecret) {
            AppKey = appKey;
            AppSecret = appSecret;
        }

        /// <summary>
        /// 微博应用创建后得到的App Key
        /// </summary>
        public string AppKey { get; internal set; }
        /// <summary>
        /// 微博应用创建后得到的App Secret
        /// </summary>
        public string AppSecret { get; internal set; }
        /// <summary>
        /// 授权后获取的Access Token
        /// </summary>
        public AccessToken AccessToken { get; internal set; }

        /// <summary>
        /// OAuth2的authorize接口
        /// </summary>
        /// <param name="response">返回类型，支持code、token，默认值为code。</param>
        /// <param name="state">用于保持请求和回调的状态，在回调时，会在Query Parameter中回传该参数。 </param>
        /// <param name="display">授权页面的终端类型，取值见下面的说明。 
        /// default 默认的授权页面，适用于web浏览器。 
        /// mobile 移动终端的授权页面，适用于支持html5的手机。 
        /// popup 弹窗类型的授权页，适用于web浏览器小窗口。 
        /// wap1.2 wap1.2的授权页面。 
        /// wap2.0 wap2.0的授权页面。 
        /// js 微博JS-SDK专用授权页面，弹窗类型，返回结果为JSONP回掉函数。
        /// apponweibo 默认的站内应用授权页，授权后不返回access_token，只刷新站内应用父框架。 
        /// </param>
        /// <returns></returns>
        private string GetAuthorizeURL(ResponseType response = ResponseType.Code, string state = null, DisplayType display = DisplayType.Default) {
            Dictionary<string, string> config = new Dictionary<string, string>()
			{
				{"client_id", AppKey},
				{"redirect_uri", ""},
				{"response_type", response.ToString().ToLower()},
				{"state", state ?? string.Empty},
				{"display", display.ToString().ToLower()}
			};
            UriBuilder builder = new UriBuilder(Properties.UrlResource.OAuth2_Authorize);
            builder.Query = Utility.BuildQueryString(config);
            return builder.ToString();
        }

        #region Http
        private async Task<string> RequestAsync(Uri uri, HttpRequestMethod method = HttpRequestMethod.Get, byte[] postData = null, string contentType = "application/x-www-form-urlencoded", IDictionary<string, string> headers = null) {
            if (!DeviceNetworkInformation.IsNetworkAvailable) throw new WebException("No network available.");
            Exception exception = null;
            WebRequest request = WebRequest.Create(uri);
            request.Headers["User-Agent"] = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)";
            if (headers != null)
                foreach (var one in headers) {
                    request.Headers[one.Key] = one.Value;
                }

            AutoResetEvent reset = new AutoResetEvent(false);
            string htmlResult = string.Empty;
            switch (method) {
                case HttpRequestMethod.Get:
                    request.Method = "GET";
                    break;
                case HttpRequestMethod.Post:
                    request.Method = "POST";
                    request.ContentType = contentType;
                    request.ContentLength = postData.Length;
                    await Task.Run(() => {
                        request.BeginGetRequestStream((ar) => {
                            using (Stream s = request.EndGetRequestStream(ar)) {
                                s.Write(postData, 0, postData.Length);
                                s.Close();
                            }
                            reset.Set();
                        }, null);
                        if (!reset.WaitOne(5000))
                            throw new WebException("尝试写入POST数据时超时。", WebExceptionStatus.Timeout);
                    });
                    break;
            }
            await Task.Run(() => {
                request.BeginGetResponse((ar) => {
                    try {
                        WebResponse response = request.EndGetResponse(ar);
                        using (StreamReader sr = new StreamReader(response.GetResponseStream())) {
                            htmlResult = sr.ReadToEnd();
                            sr.Close();
                        }
                    }
                    catch (Exception ex) { exception = ex; }
                    finally { reset.Set(); }
                }, null);
                if (!reset.WaitOne(10000))
                    throw new WebException("获取服务器数据应答超时。", WebExceptionStatus.Timeout);
            });
            if (exception != null) throw exception;

            return htmlResult;
        }
        internal async Task<string> RequestAsync(string url, HttpRequestMethod method = HttpRequestMethod.Get, params WeiboParameter[] parameters) {
            bool multi = parameters.Count(p => p.IsBinaryData) > 0;
            IDictionary<string, string> headers = new Dictionary<string, string>();
            UriBuilder uri = new UriBuilder(url);
            string contentType = string.Empty;
            byte[] postData = null;

            // 设置参数
            switch (method) {
                case HttpRequestMethod.Get:
                    uri.Query = Utility.BuildQueryString(parameters);
                    break;
                case HttpRequestMethod.Post:
                    if (multi) {
                        string boundary = Utility.GetBoundary();
                        contentType = string.Format("multipart/form-data; boundary={0}", boundary);
                        postData = Utility.BuildPostData(boundary, parameters);
                    }
                    else {
                        uri.Query = Utility.BuildQueryString(parameters);
                        contentType = "application/x-www-form-urlencoded";
                        postData = Encoding.UTF8.GetBytes(Utility.BuildQueryString(parameters));
                    }
                    break;
            }
            if (AccessToken == null)
                uri.Query = uri.Query.Length == 0 ? "source=" + AppKey : "&source=" + AppKey;
            else
                headers["Authorization"] = string.Format("OAuth2 {0}", AccessToken.Token);

            string htmlResult = string.Empty;
            try {
                return await RequestAsync(uri.Uri, method, postData, contentType, headers);
            }
            catch (WebException webEx) {
                if (webEx.Response != null) {
                    using (StreamReader reader = new StreamReader(webEx.Response.GetResponseStream())) {
                        string errorInfo = reader.ReadToEnd();
                        Error error = JsonConvert.DeserializeObject<Error>(errorInfo);
                        reader.Close();
                        if (error == null)
                            throw new WeiboException(webEx.Message, webEx);
                        else
                            throw new WeiboException(error.Code, error.Message, error.Request);
                    }
                }
                else
                    throw new WeiboException(webEx.Message, webEx);
            }
            catch { throw; }
        }
        /// <summary>
        /// 用户登陆
        /// </summary>
        /// <param name="userName">微博账号</param>
        /// <param name="password">微博密码</param>
        /// <returns></returns>
        public async Task<bool> ClientLoginAsync(string userName, string password) {
            IDictionary<string, string> headers = new Dictionary<string, string>();
            headers["Referer"] = GetAuthorizeURL();
            string postBody = string.Format("action=submit&withOfficalFlag=0&ticket=&isLoginSina=&response_type=token&regCallback=&redirect_uri={0}&client_id={1}&state=&from=&userId={2}&passwd={3}&display=js",
                "", Uri.EscapeDataString(AppKey), Uri.EscapeDataString(userName), Uri.EscapeDataString(password));
            byte[] postData = Encoding.UTF8.GetBytes(postBody);

            string htmlResult = string.Empty;
            try {
                htmlResult = await RequestAsync(new Uri(Properties.UrlResource.OAuth2_Authorize), HttpRequestMethod.Post, postData, "application/x-www-form-urlencoded", headers);
            }
            catch (WebException webEx) {
                if (webEx.Response != null) {
                    using (StreamReader reader = new StreamReader(webEx.Response.GetResponseStream())) {
                        string errorInfo = reader.ReadToEnd();
                        Error error = JsonConvert.DeserializeObject<Error>(errorInfo);
                        reader.Close();
                        if (error == null)
                            throw new WeiboException(webEx.Message, webEx);
                        else
                            throw new WeiboException(error.Code, error.Message, error.Request);
                    }
                }
                else
                    throw new WeiboException(webEx.Message, webEx);
            }
            catch { throw; }
            var pattern1 = @"\{""access_token"":""(?<token>.{0,32})"",""remind_in"":""(?<remind>\d+)"",""expires_in"":(?<expires>\d+),""uid"":""(?<uid>\d+)""\}";
            var pattern2 = @"\{""access_token"":""(?<token>.{0,32})"",""remind_in"":""(?<remind>\d+)"",""expires_in"":(?<expires>\d+),""refresh_token"":""(?<refreshtoken>.{0,32})"",""uid"":""(?<uid>\d+)""\}";
            if (!string.IsNullOrEmpty(htmlResult) && (Regex.IsMatch(htmlResult, pattern1) || Regex.IsMatch(htmlResult, pattern2))) {
                var group = Regex.IsMatch(htmlResult, "refresh_token") ? Regex.Match(htmlResult, pattern2) : Regex.Match(htmlResult, pattern1);
                AccessToken = new AccessToken {
                    Token = group.Groups["token"].Value,
                    ExpiresIn = long.Parse(group.Groups["expires"].Value),
                    UId = long.Parse(group.Groups["uid"].Value)
                };
                return true;
            }
            return false;
        }
        #endregion
        
        /// <summary>
        /// 判断AccessToken有效性
        /// </summary>
        /// <returns></returns>
        public async Task<TokenResult> VerifierAccessTokenAsync() {
            try {
                var json = await RequestAsync(Properties.UrlResource.Account_GetUid, HttpRequestMethod.Get);
            }
            catch (WeiboException ex) {
                switch (ex.ErrorCode) {
                    case 21301:
                        return TokenResult.AuthFailed;
                    case 21314:
                        return TokenResult.TokenUsed;
                    case 21315:
                        return TokenResult.TokenExpired;
                    case 21316:
                        return TokenResult.TokenRevoked;
                    case 21317:
                        return TokenResult.TokenRejected;
                    default:
                        return TokenResult.Other;
                }
            }

            return TokenResult.Success;
        }
        /// <summary>
        /// 查询用户access_token的授权相关信息，包括授权时间，过期时间和scope权限。
        /// http://open.weibo.com/wiki/Oauth2/get_token_info
        /// </summary>
        /// <returns></returns>
        public async Task<TokenInfo> GetTokenInfoAsync() {
            if (AccessToken == null) return null;
            var json = await RequestAsync(Properties.UrlResource.OAuth2_GetTokenInfo, HttpRequestMethod.Post, new WeiboParameter("access_token", AccessToken.Token));
            return JsonConvert.DeserializeObject<TokenInfo>(json);
        }
    }
}
